/**
*  Copyright (c) 2011, Alex Theodoridis
*  All rights reserved.

*  Redistribution and use in source and binary forms, with
*  or without modification, are permitted provided that the
*  following conditions are met:
*  Redistributions of source code must retain the above
*  copyright notice, this list of conditions and the following disclaimer.
*  Redistributions in binary form must reproduce the above
*  copyright notice, this list of conditions and the following
*  disclaimer in the documentation and/or other materials
*  provided with the distribution.

*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
*  AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
*  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
*  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
*  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
*  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
*  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
*  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE,
*  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
*/

#ifndef XMLNEURALLAYERINSTREAM_H
#define XMLNEURALLAYERINSTREAM_H
#include <NeuralNetwork/Serialization/INeuralLayerStream.h>
#include <NeuralNetworkXmlSerializer/XmlNeuronStream.h>
#include <NeuralNetwork/Serialization/NeuralLayerMemento.h>
#include <Utilities/SmartPtr/SharedPtr.h>
#include <Utilities/StrUtil/StrUtil.h>
#include <tinyxml/tinyxml.h>
#include <algorithm>

namespace nn {

    template<class Var>
    class NeuralLayer;

    template<class Var>
    class IXmlVarStream;

    template<class Var>
    class XmlNeuralLayerStream : public INeuralLayerStream<Var> {
    public:
        static const std::string CONST_NEURAL_LAYER_NODE_NAME;
        static const std::string CONST_OUTPUT_LAYER_NODE_NAME;

    private:
        NeuralLayerMemento<Var> _memento;
        utils::SharedPtr<IXmlVarStream<Var> > _varStream;
        bool _isOutputLayer;

    public:
        XmlNeuralLayerStream ( TiXmlElement* layerNode, const utils::SharedPtr<IXmlVarStream<Var> >& varStream, bool isOutputLayer = false ){
            if ( varStream==NULL ) {
                throw bad_stream ( "Wrong argument varStream == NULL", __FILE__, __LINE__ );
            }

            if ( layerNode == NULL ) {
                throw bad_stream ( "Wrong argument layerNode == NULL", __FILE__, __LINE__ );
            }

            if ( CONST_NEURAL_LAYER_NODE_NAME != layerNode->Value() && CONST_OUTPUT_LAYER_NODE_NAME != layerNode->Value()  ) {
                throw bad_stream ( "Wrong argument Invalid node name", __FILE__, __LINE__ );
            }

            std::map< unsigned int, NeuronMemento<Var> > neurons;
            TiXmlElement* neuronNode = layerNode->FirstChildElement ( XmlNeuronStream<Var>::CONST_NEURON_NODE_NAME.c_str() );
            while ( neuronNode != NULL ) {
                XmlNeuronStream<Var> neuronStream ( neuronNode, varStream );
                if ( !neurons.insert ( std::pair< unsigned int, NeuronMemento<Var> > ( neurons.size(), neuronStream.getMemento() ) ).second ) {
                    throw bad_stream ( "Neurons list allocation error", __FILE__, __LINE__ );
                }

                neuronNode = neuronNode->NextSiblingElement ( XmlNeuronStream<Var>::CONST_NEURON_NODE_NAME.c_str() );
            }

            if ( neurons.empty() ) {
                throw bad_stream ( "Invalid argument layerNode with no Neurons", __FILE__, __LINE__ );
            }

            _memento.setNeurons ( neurons );
            _varStream=varStream;
            _isOutputLayer = isOutputLayer;
        }


        XmlNeuralLayerStream ( const NeuralLayer<Var>& neuralLayer, const utils::SharedPtr<IXmlVarStream<Var> >& varStream, bool isOutputLayer = false ){
            if ( varStream == NULL ) {
                throw bad_stream ( "Invalid argument varStream == NULL", __FILE__, __LINE__ );
            }

            _memento=neuralLayer.getMemento();
            _varStream=varStream;
            _isOutputLayer;
        }


        XmlNeuralLayerStream ( const NeuralLayerMemento<Var> memento, const utils::SharedPtr<IXmlVarStream<Var> >& varStream, bool isOutputLayer = false ){
            if ( varStream == NULL ) {
                throw bad_stream ( "Invalid argument varStream == NULL", __FILE__, __LINE__ );
            }

            if ( memento.getNeurons().size() == 0 ) {
                throw bad_stream ( "Invalid argument memento", __FILE__, __LINE__ );
            }

            _memento=memento;
            _varStream=varStream;
            _isOutputLayer = isOutputLayer;
        }


        const NeuralLayerMemento<Var>& getMemento() const {
            return _memento;
        }


        NeuralLayer<Var>& operator >> ( NeuralLayer<Var>& neuralLayer ) const {
            neuralLayer.setMemento ( _memento );
            return neuralLayer;
        }

        const TiXmlDocument& operator >> ( TiXmlDocument& document ) {
            document.Clear();
            
            TiXmlElement* layerNode = new TiXmlElement ( _isOutputLayer ? CONST_OUTPUT_LAYER_NODE_NAME.c_str() : CONST_NEURAL_LAYER_NODE_NAME.c_str() );
            document.LinkEndChild ( layerNode );
            std::map< unsigned int, NeuronMemento<Var> > neurons = _memento.getNeurons();
            typename std::map< unsigned int, NeuronMemento<Var> >::const_iterator curNeuron;
            for ( curNeuron = neurons.begin(); curNeuron != neurons.end(); curNeuron++ ) {
                TiXmlDocument neuronDocument;
                XmlNeuronStream<Var> stream ( curNeuron->second, _varStream );
                stream >> neuronDocument;
                TiXmlElement* element = neuronDocument.FirstChildElement ( XmlNeuronStream<Var>::CONST_NEURON_NODE_NAME.c_str() )->Clone()->ToElement();
                layerNode->LinkEndChild ( element );
            }

            return document;
        }


        virtual ~XmlNeuralLayerStream() {

        }
    };

    template<class Var>
    const std::string XmlNeuralLayerStream<Var>::CONST_NEURAL_LAYER_NODE_NAME = "NeuralLayer";
    
    template<class Var>
    const std::string XmlNeuralLayerStream<Var>::CONST_OUTPUT_LAYER_NODE_NAME = "OutputNeuralLayer";

}

#endif // XMLNEURALLAYERINSTREAM_H
// kate: indent-mode cstyle; space-indent on; indent-width 4; 
